#pragma once
#include "BBox.h"
#include "Matrices.h"
#include "Vector3f.h"
#include <vector>
#include "Object.h"

class BVH;

class Frustum
{
private:
	static Frustum* f;
	//six planes Ax+by+Cz+D=0
	Vector3f frustum[6];
	Vector3f origin_frustum[6];
	//8 vertices of the frustum
	float vertices[8][3];

	// members
	int windowWidth;
	int windowHeight;
	bool windowSizeChanged;
	bool drawModeChanged;
	int drawMode;
	int mouseX;
	int mouseY;
	float cameraPosition[3];
	float cameraAngle[3];
	float modelPosition[3];
	float modelAngle[3];

	// these are for 3rd person view
	float cameraAngleX;
	float cameraAngleY;
	float cameraDistance;
	float bgColor[4];

	// 4x4 transform matrices
	Matrix4 matrixView;
	Matrix4 matrixModel;
	Matrix4 matrixModelView;
	Matrix4 matrixProjection;

	std::vector<Object*>* objects;

	BVH* bvh;
private:
	void initLights();
	void drawGrid(float size, float step);          // draw a grid on XZ plane
	void drawAxis(float size);                      // draw 3 axis
	void drawFrustum();
	Matrix4 setFrustum(float l, float r, float b, float t, float n, float f);
	Matrix4 setFrustum(float fovy, float ratio, float n, float f);

public:
	static enum planes
	{
		RIGHT_P = 0, LEFT_P, BOTTOM_P, TOP_P, FAR_P, NEAR_P
	};
	static enum isVisible
	{
		VISIBLE, INVISIBLE
	};
public:
	Frustum();
	~Frustum();
	void init(float size = 10, float step = 1);

	void setCameraX(float x) { cameraPosition[0] = x; updateViewMatrix(); }
	void setCameraY(float y) { cameraPosition[1] = y; updateViewMatrix(); }
	void setCameraZ(float z) { cameraPosition[2] = z; updateViewMatrix(); }

	void setModelX(float x) { modelPosition[0] = x; updateModelMatrix(); }
	void setModelY(float y) { modelPosition[1] = y; updateModelMatrix(); }
	void setModelZ(float z) { modelPosition[2] = z; updateModelMatrix(); }

	void setCameraAngleX(float p) { cameraAngle[0] = p; updateViewMatrix(); }
	void setCameraAngleY(float h) { cameraAngle[1] = h; updateViewMatrix(); }
	void setCameraAngleZ(float r) { cameraAngle[2] = r; updateViewMatrix(); }

	void incCameraX() { cameraPosition[0]++; updateViewMatrix(); }
	void decCameraX() { cameraPosition[0]--; updateViewMatrix(); }

	void incCameraY() { cameraPosition[1]++; updateViewMatrix(); }
	void decCameraY() { cameraPosition[1]--; updateViewMatrix(); }

	void incCameraZ() { cameraPosition[2]++; updateViewMatrix(); }
	void decCameraZ() { cameraPosition[2]--; updateViewMatrix(); }


	void incCameraRotateX() { cameraAngle[0]++; updateViewMatrix(); }
	void decCameraRotateX() { cameraAngle[0]--; updateViewMatrix(); }

	void incCameraRotateY() { cameraAngle[1]++; updateViewMatrix(); }
	void decCameraRotateY() { cameraAngle[1]--; updateViewMatrix(); }

	void incCameraRotateZ() { cameraAngle[2]++; updateViewMatrix(); }
	void decCameraRotateZ() { cameraAngle[2]--; updateViewMatrix(); }


	void incModelX() { modelPosition[0]++; updateModelMatrix(); }
	void decModelX() { modelPosition[0]--; updateModelMatrix(); }

	void incModelY() { modelPosition[1]++; updateModelMatrix(); }
	void decModelY() { modelPosition[1]--; updateModelMatrix(); }

	void incModelZ() { modelPosition[2]++; updateModelMatrix(); }
	void decModelZ() { modelPosition[2]--; updateModelMatrix(); }

	void setCamera(float posX, float posY, float posZ, float targetX, float targetY, float targetZ);
	void setViewMatrix(float x, float y, float z, float pitch, float heading, float roll);
	void setWindowSize(int width, int height);

	void draw();
	
	static void render();
	void getFrustumNormalAndDistance(float fovY, float aspectRatio, float nearPlane, float farPlane);
	void getNormal(float *v1, float *v2, float *v3, Vector3f& plane);

	isVisible BoxInFrustum(const Vector3f &bbMin, const Vector3f &bbMax) const;
	float distance(const Vector3f &point, planes plane) const;
	void updateViewMatrix();
	void updateModelMatrix();
	void setObjects(std::vector<Object*>* _objects) { this->objects = _objects; }
	void setBVH(BVH* _bvh) { bvh = _bvh; }
};
